package evemanutool.utils.datahandling; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import evemanutool.constants.ErrorConstants; import evemanutool.data.general.Pair; import evemanutool.gui.main.EMT; import evemanutool.utils.exceptions.ServerException; public class DatabaseHandler extends ThreadedHandler { //Constants. public enum Stage {RAW(0), NESTED(1), DERIVED(2), PROCESS(3), COMPUTE(4), SAVE(5); public int key; Stage(int key){this.key = key;} } public enum State {RUNNING, WAITING} //Databases. private ArrayList<Database> databases = new ArrayList<>(); //GUI updates. private ConcurrentHashMap<Pair<Database, Stage>, ArrayList<GUIUpdater>> gUpdates = new ConcurrentHashMap<>(); //GUI disables. private ConcurrentHashMap<Database, GUIDisabler> gDisables = new ConcurrentHashMap<>(); //ThreadPool. private ExecutorService es; //Object locks. private final Object dbLock = new Object(); public DatabaseHandler() {} public void addDatabase(Database db) { //Only add if handler isn't running. if (!isRunning()) { synchronized (dbLock) { databases.add(db); //Set initial values for the database. db.setStage(Stage.RAW); db.setState(State.WAITING); } } } public void addGUIDisabler(GUIDisabler gD, Database db) { //Only add if handler isn't running. if (!isRunning()) { //Add / Overwrite the GUIDisabler. gDisables.put(db, gD); } } public void addGUIUpdater(GUIUpdater gU, Stage stage, Database db) { //Only add if handler isn't running. if (!isRunning()) { //Add the GUIUpdater to the right key-entry. Pair<Database, Stage> key = new Pair<>(db, stage); if (gUpdates.get(key) == null) { ArrayList<GUIUpdater> tmpL = new ArrayList<>(); gUpdates.put(key, tmpL); } gUpdates.get(key).add(gU); } } public void addGUIUpdaters(Collection<GUIUpdater> l, Stage stage, Database db) { for (GUIUpdater gU : l) { addGUIUpdater(gU, stage, db); } } @Override public void init() { if (!isRunning()) { //Status. setRunning(true); //Create the ThreadPool. es = Executors.newCachedThreadPool(); //Execute main scheduler task. es.execute(new DBTaskScheduler()); System.out.println("DataHandler Initialized."); } } @Override public void exit() { if (isRunning()) { es.shutdownNow(); //Create a new ThreadPool. es = Executors.newCachedThreadPool(); synchronized (dbLock) { for (Database db : databases) { if (db.needsTermination() && db.isComplete()) { launchTask(db, Stage.SAVE); } } } es.shutdown(); try { es.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void kill() { if (isRunning()) { es.shutdownNow(); synchronized (dbLock) { for (Database db : databases) { db.kill(); } } } } public void reportDBUpdateAtStage(Database db, Stage stage) { handleDatabaseUpdate(db, stage); } public boolean updateDBAtStage(Database db, Stage stage) { //Only update if database is past the stage or is executing it currently. if (db.getStage().key > stage.key || (db.getStage() == stage && db.getState() == State.RUNNING)) { //Setup database for update. db.setStage(stage); db.setState(State.WAITING); handleDatabaseUpdate(db, stage); return true; } return false; } private void handleDatabaseUpdate(Database db, Stage stage) { Stage updateStage; //Don't update other Databases if it hasn't reached the stage preceding the firstProvidingStage. if (db.isProvider() && db.getFirstProividingStage().key <= getNextStage((updateStage = stage)).key) { updateStage = updateStage.key > db.getFirstProividingStage().key ? updateStage : db.getFirstProividingStage(); synchronized (dbLock) { //Update other databases if needed. for (Database itrDb : databases) { if (itrDb.getFinalStage().key >= updateStage.key && (itrDb.getStage().key > updateStage.key || (db.getStage() == updateStage && db.getState() == State.RUNNING))) { //Ready databases to be included in next task launch. //Synchronized init-methods should keep data safe //from multiple stages being executed concurrently. itrDb.setStage(updateStage); itrDb.setState(State.WAITING); } } } } } private void launchGUIUpdates(Database db, Stage stage) { //Create key. Pair<Database, Stage> key = new Pair<>(db, stage); final ArrayList<GUIUpdater> l = gUpdates.get(key); if (l != null) { //Schedule GUI thread to do updates. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { for (GUIUpdater gU : l) { System.out.println("GUI updated: " + gU.getClass().getSimpleName()); gU.updateGUI(); } } }); } } private void launchGUIDisabler(final Database db) { //Create key. final GUIDisabler gD = gDisables.get(db); if (gD != null) { //Schedule GUI thread to do updates. SwingUtilities.invokeLater(new Runnable() { @Override public void run() { System.out.println("GUI disabled: " + db.getClass().getSimpleName()); gD.disableGUI(); } }); } } private void launchTask(Database db, Stage stage) { db.setStage(stage); db.setState(State.RUNNING); es.execute(new DBWorker(db, stage)); } private ArrayList<Database> getReadyDatabases() { int min = Stage.COMPUTE.key; //Last possible stage. ArrayList<Database> ans = new ArrayList<>(); synchronized (dbLock) { //Find the earliest unfinished stage of the Databases. for (Database itrDb : databases) { if ( itrDb.getStage().key < min && !itrDb.pastFinalStage() && itrDb.isProvider() && itrDb.isEnabled()) { min = itrDb.getStage().key; } } //Add all earlier or equal and waiting Databases. for (Database itrDb : databases) { if ( !itrDb.pastFinalStage() && itrDb.getState() == State.WAITING && itrDb.getStage().key <= min && itrDb.isEnabled()) { ans.add(itrDb); } } } return ans; } private Stage getNextStage(Database db) { //Returns the next stage in Stage Enum. return Stage.values()[Stage.SAVE.key > db.getStage().key ? db.getStage().key + 1 : db.getStage().key]; } private Stage getNextStage(Stage stage) { //Returns the next stage in Stage Enum. return Stage.values()[Stage.SAVE.key > stage.key ? stage.key + 1 : stage.key]; } private class DBTaskScheduler implements Runnable{ //Constants. public static final long REFRESH_DELAY = 100; //ms @Override public void run() { while (!Thread.interrupted()) { //Launch the readied tasks. for (Database db : getReadyDatabases()) { System.out.println("Task launched: " + db.getClass().getSimpleName() + " / " + db.getStage()); launchTask(db, db.getStage()); } try { //Wait until next update. Thread.sleep(REFRESH_DELAY); } catch (InterruptedException e) { //End thread. return; } } } } private class DBWorker implements Runnable, ErrorConstants { private Database db; private Stage stage; public DBWorker(Database db, Stage stage) { this.db = db; this.stage = stage; } @Override public void run() { //Switch depending on the stage. try { switch (stage) { case RAW: db.loadRawData(); break; case NESTED: db.loadNestedData(); break; case DERIVED: db.loadDerivedData(); break; case PROCESS: db.processData(); break; case COMPUTE: db.computeData(); break; case SAVE: db.saveData(); break; default: break; } } catch (Exception e) { System.out.println("Exception during task: " + db.getClass().getSimpleName() + "-" + db.getStage()); //Check for expected errors to give the user feedback. if (e instanceof ServerException) { EMT.M_HANDLER.addMessage(((ServerException) e).getUserErrorMessage()); } else { //Unexpected exception. e.printStackTrace(); } //An error has occurred and the database is disabled. db.setEnabled(false); db.setState(State.WAITING); //Database is critical to application, terminate with message. if (db.isProvider()) { //Shows the message, and kills application. JOptionPane.showMessageDialog(EMT.MAIN, CRITICAL_APPLICATION_ERROR_MESSAGE, "Error", JOptionPane.ERROR_MESSAGE); EMT.MAIN.killApp(); } else { //Disable GUI if database isn't a provider. launchGUIDisabler(db); } return; } System.out.println("Task finished: " + db.getClass().getSimpleName() + " / " + stage); //Launch GUI updates if needed. launchGUIUpdates(db, stage); //Only change status of the database if no changes has occurred during execution. if (db.getStage() == stage && db.getState() == State.RUNNING) { //Change the State and Stage on completion. db.setStage(getNextStage(db)); db.setState(State.WAITING); } } } }